﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Windows.Forms;

internal abstract partial class RefCountedCache<TObject, TCacheEntryData, TKey>
{
    /// <summary>
    ///  Disposable struct that manages reference counting of <see cref="CacheEntry"/>.
    /// </summary>
#if DEBUG
    internal class Scope : DisposalTracking.Tracker, IDisposable
#else
    internal readonly ref struct Scope
#endif
    {
        private readonly TObject _object;
        private CacheEntry Entry { get; }

        /// <summary>
        ///  Constructor to hold an uncached object. Used to wrap something not coming from the cache in a scope
        ///  so it can be abstracted for the end users of a given API.
        ///
        ///  See <see cref="GdiPlusCache.GetSolidBrushScope(Drawing.Color)"/> for an example.
        /// </summary>
        /// <remarks>
        ///  <para>
        ///   Currently we don't dispose the <paramref name="object"/> as we don't need to in our usages. If this
        ///   becomes necessary we can add a bool to track whether or not we should dispose it.
        ///  </para>
        /// </remarks>
        public Scope(TObject @object)
        {
            Entry = default!;
            _object = @object;
        }

        public Scope(CacheEntry entry)
        {
            Debug.Assert(entry is not null);
            _object = default!;
            Entry = entry;
            Entry.AddRef();
        }

        [MaybeNull]
        public TCacheEntryData Data => Entry is null ? default : Entry.Data;
        public TObject Object => this;
        public int RefCount => Entry?.RefCount ?? -1;

        /// <summary>
        ///  Implicit conversion to the "target" type, i.e. <typeparamref name="TObject"/>.
        /// </summary>
        /// <remarks>
        ///  <para>
        ///   This is somewhat dangerous as implicit casting in the using statement will leak the scope. Not doing
        ///   this, however, makes usage with APIs difficult. We track in DEBUG to catch misuse as a mitigation.
        ///  </para>
        /// </remarks>
        public static implicit operator TObject(in Scope scope)
        {
#if DEBUG
            // In DEBUG the scope is a class and we create "default" scopes in some cases.
            if (scope is null)
            {
                return default!;
            }
#endif

            CacheEntry entry = scope.Entry;
            return entry is null ? scope._object : entry.Object;
        }

        public void Dispose()
        {
            Entry?.RemoveRef();
            DisposalTracking.SuppressFinalize(this!);
        }

#if DEBUG
        // Only need to define this constructor when we are a class
        internal Scope()
        {
            Entry = default!;
            _object = default!;
        }
#endif
    }
}
